/* Sentinel - IRC Statistical and Operator Services
** hosthash.c - Functions to manage host and domain hashes
**
** Copyright W. Campbell and others.  See README for more details
** Some code Copyright: Jonathan George, Kai Seidler, ircd-hybrid Team,
**                      IRCnet IRCD developers.
**
** $Id: hosthash.c 7 2010-02-01 19:17:42Z dubkat $
*/

#include "stats.h"
#include "struct.h"
#include "proto.h"
#include "match.h"
#include "slog.h"
#include "s_string.h"
#include "mem.h"

#ifdef MONSERV

static void add_hashent_to_hash_table(struct HashEnt **, char *,
                                      struct HashEnt *);
static void del_hashent_from_hash_table(struct HashEnt **, struct HashEnt *);
static struct HashEnt *newhashent(struct HashEnt **, char *);

struct HashEnt *hostlist[H_TABLE_SIZE];
struct HashEnt *domainlist[H_TABLE_SIZE];
struct HashEnt *uhlist[H_TABLE_SIZE];

static void add_hashent_to_hash_table(struct HashEnt **ht, char *key,
                                      struct HashEnt *h)
{
  h->hash = HASH((unsigned char *) key, U_TABLE_SIZE);
  h->next = ht[h->hash];
  ht[h->hash] = h;
}

static void del_hashent_from_hash_table(struct HashEnt **ht, struct HashEnt *h)
{
  struct HashEnt *tmp, *prev = NULL;

  for (tmp = ht[h->hash]; tmp; tmp = tmp->next)
  {
    if (tmp == h)
    {
      if (prev)
        prev->next = tmp->next;
      else
      {
        ht[h->hash] = tmp->next;
      }
      tmp->next = NULL;
      return ;
    }
    prev = tmp;
  }
}

static struct HashEnt *newhashent(struct HashEnt **ht, char *key)
{
  struct HashEnt *he;

  he = smalloc(sizeof(struct HashEnt));
  memcounts.hashent++;
  strlcpy(he->hashval, key, MAXUSERHOST);
  add_hashent_to_hash_table(ht, key, he);

  return he;
}

void get_domain(char *key, char *hostname)
{
  char *dptr;
  char tbuf[MAXHOST];
  int len;
  char *restoreptr;

  strlcpy(tbuf, hostname, MAXHOST);
  len = strlen(tbuf);

  dptr = strchr(tbuf, ':');
  if (dptr != NULL)
  {
#ifdef AF_INET6
    struct in6_addr iaddr6;

    /* If there's a '.' at the end of the IPv6 IP, that will cause problems
    ** with some inet_pton()'s.  Remove it.  This happens if dot_in_ip6_addr
    ** is set on in Hybrid 7/ircd-ratbox.
    */
    if (tbuf[len - 1] == '.')
    {
      tbuf[len - 1] = '\0';
      /* Not really needed:  len--; */
    }
    inet_pton(AF_INET6, tbuf, &iaddr6);
    /* This is cheating...apply a /96 mask by clearing
    ** a value in the in6_addr
    ** (or /64)
    */
    /* Ugh, it appears the only compatible thing with in6_addr
    ** is the name s6_addr.  It LOOKS like most OS's define
    ** s6_addr as an array of 16 uint8_t's
    */
#ifdef MON_V6_64_MASK

    iaddr6.s6_addr[8] = 0;
    iaddr6.s6_addr[9] = 0;
    iaddr6.s6_addr[10] = 0;
    iaddr6.s6_addr[11] = 0;
#endif

    iaddr6.s6_addr[12] = 0;
    iaddr6.s6_addr[13] = 0;
    iaddr6.s6_addr[14] = 0;
    iaddr6.s6_addr[15] = 0;

    inet_ntop(AF_INET6, &iaddr6, tbuf, MAXUSERHOST);
    strlcpy(key, tbuf, MAXUSERHOST);
#else
    /* XXX Choose a better way to handle when we can't deal with IPv6
    ** addresses (Cygwin)
    */
    strlcpy(key, hostname, MAXUSERHOST);
#endif

    return ;
  }

  dptr = strrchr(tbuf, '.');
  if (dptr == NULL)
  {
    /* No . at all.... something is screwed
    ** OR...
    ** it's a Bahamut oper spoof
    */
    strlcpy(key, hostname, MAXUSERHOST);
    return ;
  }
  /* EWWWW, pointer arithmetic! */
  if ( (dptr - tbuf) == (len - 1) )
  {
    /* This happens to be a domain like "services.", I'll put the
    ** whole domain into the key for simplicity
    */
    strlcpy(key, hostname, MAXUSERHOST);
    return ;
  }
  /* Check the tld for a numeric value.  If it is, it's a PRETTY
  ** good chance it's a nonresolving IP.  For now, use the /24 prefix
  */
  if (isdigit((int)*(dptr + 1)))
  {
    *dptr = '\0';
    strlcpy(key, tbuf, MAXUSERHOST);
    return ;
  }
  /* Now check to see if it is a simple domain, "botbay.net" for
  ** instance
  */
  *dptr = ',';
  restoreptr = dptr;
  dptr = strrchr(tbuf, '.');
  if (dptr == NULL)
  {
    /* It is just a simple domain */
    strlcpy(key, hostname, MAXUSERHOST);
    return ;
  }
  /* Now we have the domain pointer */
  *restoreptr = '.';
  dptr++;
  strlcpy(key, dptr, MAXUSERHOST);
}

/* Add the user entry to the three "clone" hash tables. */
void AddUserToHashes(struct User *u)
{
  char tbuf[MAXUSERHOST];
  struct HashEnt *he;
  dlink_node *dl;
  char *userptr;
  char *hostptr;
  char thostbuf[MAXUSERHOST];
  char *sptr;

  /* Any client on an ignored server is dropped right here. */
  DLINK_FOREACH(dl, MonServ.ignore_server_list.head)
  {
    sptr = dl->data;
    if (match(sptr, u->server->name) == 0)
      return ;
  }

  if (strchr(u->hostname, ':'))
  {
    get_domain(thostbuf, u->hostname);
    /* Add the CIDR mask */
#ifdef MON_V6_64_MASK

    strlcat(thostbuf, "/64", MAXUSERHOST);
#else

    strlcat(thostbuf, "/96", MAXUSERHOST);
#endif

    hostptr = thostbuf;
  }
  else
  {
    hostptr = u->hostname;
  }

#ifdef MON_IDENT_CLONES
  /* First the user@host hash table, with idented users set to
  ** ~*@ (or in 2.10, -*@)
  */
  if (u->username[0] == '~')
    userptr = "~*";
  else
    userptr = u->username;
#else

  userptr = u->username;
#endif

  snprintf(tbuf, MAXUSERHOST, "%s@%s", userptr, hostptr);

  he = findhashent(uhlist, tbuf);
  if (he == NULL)
    he = newhashent(uhlist, tbuf);

  dl = dlink_create();
  dlink_add(u, dl, &he->userlist);
  he->count++;

  /* Then the hostname hash */
  strlcpy(tbuf, u->hostname, MAXUSERHOST);

  he = findhashent(hostlist, tbuf);
  if (he == NULL)
    he = newhashent(hostlist, tbuf);

  dl = dlink_create();
  dlink_add(u, dl, &he->userlist);
  he->count++;

  /* And finally the domain hash */
  get_domain(tbuf, u->hostname);

  he = findhashent(domainlist, tbuf);
  if (he == NULL)
    he = newhashent(domainlist, tbuf);

  dl = dlink_create();
  dlink_add(u, dl, &he->userlist);
  he->count++;
}

void DelUserFromHashes(struct User *u)
{
  char tbuf[MAXUSERHOST];
  char thostbuf[MAXUSERHOST];
  char *hostptr;
  struct HashEnt *he;
  dlink_node *dl;

  /* We still must process this even if the user is on a CURRENTLY
  ** ignored server, as the list can be rehashed.
  */

  if (strchr(u->hostname, ':'))
  {
    get_domain(thostbuf, u->hostname);
    /* Add the CIDR mask */
#ifdef MON_V6_64_MASK

    strlcat(thostbuf, "/64", MAXUSERHOST);
#else

    strlcat(thostbuf, "/96", MAXUSERHOST);
#endif

    hostptr = thostbuf;
  }
  else
  {
    hostptr = u->hostname;
  }

#ifdef MON_IDENT_CLONES
  /* First the user@host hash table, with idented users set to
  ** ~*@ (or in 2.10, -*@)
  */
  if (u->username[0] == '~')
    snprintf(tbuf, MAXUSERHOST, "~*@%s", hostptr);
  else
    snprintf(tbuf, MAXUSERHOST, "%s@%s", u->username, hostptr);
#else

  snprintf(tbuf, MAXUSERHOST, "%s@%s", u->username, hostptr);
#endif

  he = findhashent(uhlist, tbuf);
  if (he != NULL)
  {
    dl = dlink_find_delete(u, &he->userlist);
    if (dl != NULL)
    {
      dlink_free(dl);
      he->count--;
      if (he->count == 0)
      {
        del_hashent_from_hash_table(uhlist, he);
        memcounts.hashent--;
        free(he);
      }
    }
  }

  /* Then the hostname hash */
  strlcpy(tbuf, u->hostname, MAXUSERHOST);

  he = findhashent(hostlist, tbuf);
  if (he != NULL)
  {
    dl = dlink_find_delete(u, &he->userlist);
    if (dl != NULL)
    {
      dlink_free(dl);
      he->count--;
      if (he->count == 0)
      {
        del_hashent_from_hash_table(hostlist, he);
        memcounts.hashent--;
        free(he);
      }
    }
  }

  /* And finally the domain hash */
  get_domain(tbuf, u->hostname);

  he = findhashent(domainlist, tbuf);
  if (he != NULL)
  {
    dl = dlink_find_delete(u, &he->userlist);
    if (dl != NULL)
    {
      dlink_free(dl);
      he->count--;
      if (he->count == 0)
      {
        del_hashent_from_hash_table(domainlist, he);
        memcounts.hashent--;
        free(he);
      }
    }
  }
}

struct HashEnt *findhashent(struct HashEnt **ht, char *key)
{
  struct HashEnt *he;

  he = ht[HASH((unsigned char *) key, H_TABLE_SIZE)];
  while (he && (irccasecmp(he->hashval, key) != 0))
    he = he->next;

  return he;
}

void init_host_hashes(void)
{
  int i;
  struct HashEnt *he, *prev;
  dlink_node *dl, *tdl;

  for (i = 0; i < H_TABLE_SIZE; i++)
  {
    he = uhlist[i];
    while (he != NULL)
    {
      prev = he->next;
      debug("init_host_hashes() deleting %s", he->hashval);
      DLINK_FOREACH_SAFE(dl, tdl, he->userlist.head)
      {
        dlink_free(dl);
      }
      free(he);

      he = prev;
    }
    uhlist[i] = NULL;
  }

  for (i = 0; i < H_TABLE_SIZE; i++)
  {
    he = hostlist[i];
    while (he != NULL)
    {
      prev = he->next;
      debug("init_host_hashes() deleting %s", he->hashval);
      DLINK_FOREACH_SAFE(dl, tdl, he->userlist.head)
      {
        dlink_free(dl);
      }
      free(he);

      he = prev;
    }
    hostlist[i] = NULL;
  }

  for (i = 0; i < H_TABLE_SIZE; i++)
  {
    he = domainlist[i];
    while (he != NULL)
    {
      prev = he->next;
      debug("init_host_hashes() deleting %s", he->hashval);
      DLINK_FOREACH_SAFE(dl, tdl, he->userlist.head)
      {
        dlink_free(dl);
      }
      free(he);

      he = prev;
    }
    domainlist[i] = NULL;
  }
}

#ifdef MON_DRONES 
/* This one must always be called first, the others check if this one
** enterred the drone into the drone list
*/
void CheckDrones(struct User *u)
{
  struct DroneEntry *de;
  dlink_node *dl;
  dlink_node *dadd;
  struct DroneNode *dn;

  if (IsOper(u))
    return ;

  DLINK_FOREACH(dl, MonServ.username_drone_list.head)
  {
    de = dl->data;
    if (match(de->name, u->username) == 0)
    {
      dadd = dlink_create();
      dn = smalloc(sizeof(struct DroneNode));
      memcounts.dronenode++;
      dn->u = u;
      snprintf(dn->reason, LINE, "username %s", de->name);
      dlink_add(dn, dadd, &MonServ.drone_users);
      de->hits++;
#if defined(DRONES_ALERT)
#ifdef DRONES_CHANNEL

      dronealert("DRONE MATCH:  %s!%s@%s username mask: %s",
#else
      alert("DRONE MATCH:  %s!%s@%s username mask: %s",
#endif
                 u->nick, u->username, u->hostname,
                 de->name);
#endif
      slog(DRONE_LOG, L_NOTICE, "UN %s!%s@%s %s", u->nick, u->username,
           u->hostname, de->name);
      return ;
    }
  }
  DLINK_FOREACH(dl, MonServ.nick_drone_list.head)
  {
    de = dl->data;
    if (match(de->name, u->nick) == 0)
    {
      dadd = dlink_create();
      dn = smalloc(sizeof(struct DroneNode));
      memcounts.dronenode++;
      dn->u = u;
      snprintf(dn->reason, LINE, "nick %s", de->name);
      dlink_add(dn, dadd, &MonServ.drone_users);
      de->hits++;
#if defined(DRONES_ALERT)
#ifdef DRONES_CHANNEL

      dronealert("DRONE MATCH:  %s!%s@%s nick mask: %s",
#else
      alert("DRONE MATCH:  %s!%s@%s nick mask: %s",
#endif
                 u->nick, u->username, u->hostname,
                 de->name);
#endif
      slog(DRONE_LOG, L_NOTICE, "NI %s!%s@%s %s", u->nick, u->username,
           u->hostname, de->name);
      return ;
    }
  }
}

void CheckGecosDrones(struct User *u, char *gecos)
{
  struct DroneEntry *de;
  struct DroneNode *dn;
  dlink_node *dl;
  dlink_node *dadd;

  if (IsOper(u))
    return ;

  DLINK_FOREACH(dl, MonServ.drone_users.head)
  {
    dn = dl->data;

    if (dn->u == u)
      return ;
  }

  if (IsMonNickGecos())
  {
    if (strislower(u->nick) && (irccmp(u->nick, gecos) == 0))
    {
      dadd = dlink_create();
      dn = smalloc(sizeof(struct DroneNode));
      memcounts.dronenode++;
      dn->u = u;
      strcpy(dn->reason, "nick == gecos");
      dlink_add(dn, dadd, &MonServ.drone_users);
#if defined(DRONES_ALERT)
#ifdef DRONES_CHANNEL

      dronealert("DRONE MATCH:  %s!%s@%s nick == gecos",
#else
      alert("DRONE MATCH:  %s!%s@%s nick == gecos",
#endif
                 u->nick, u->username, u->hostname);
#endif
      slog(DRONE_LOG, L_NOTICE, "NG %s!%s@%s *", u->nick, u->username,
           u->hostname);
      return ;
    }
  }
  if (IsTrackFizzer())
  {
    char testuser[MAXUSER + 1];
    char *tmpgecos = sstrdup(gecos);
    char *secondptr;
    char *tst;

    secondptr = strchr(tmpgecos, ' ');
    if (secondptr != NULL)
    {
      *secondptr = '\0';
      secondptr++;

      tst = strchr(tmpgecos, ' ');
      if (tst == NULL)  /* fizzers always seem to be exactly two names long */
      {
        snprintf(testuser, MAXUSER + 1, "~%s%s", secondptr, tmpgecos);
        if (irccmp(u->username, testuser) == 0)
        {
          dadd = dlink_create();
          dn = smalloc(sizeof(struct DroneNode));
          memcounts.dronenode++;
          dn->u = u;
          strcpy(dn->reason, "possible fizzer");
          dlink_add(dn, dadd, &MonServ.drone_users);
#if defined(DRONES_ALERT)
#ifdef DRONES_CHANNEL

          dronealert("DRONE MATCH:  %s!%s@%s possible fizzer",
#else
          alert("DRONE MATCH:  %s!%s@%s possible fizzer",
#endif
                     u->nick, u->username, u->hostname);
#endif
          slog(DRONE_LOG, L_NOTICE, "FZ %s!%s@%s *", u->nick, u->username,
               u->hostname);
          free(tmpgecos);
          return ;
        }
      }
    }
    free(tmpgecos);
  }
  DLINK_FOREACH(dl, MonServ.gecos_drone_list.head)
  {
    de = dl->data;
    if (match(de->name, gecos) == 0)
    {
      dadd = dlink_create();
      dn = smalloc(sizeof(struct DroneNode));
      memcounts.dronenode++;
      dn->u = u;
      snprintf(dn->reason, LINE, "gecos %s", de->name);
      dlink_add(dn, dadd, &MonServ.drone_users);
      /* Match one, it's useless to test anything else...
      ** it's already a drone!
      */
      de->hits++;
#if defined(DRONES_ALERT)
#ifdef DRONES_CHANNEL

      dronealert("DRONE MATCH:  %s!%s@%s gecos mask: %s",
#else
      alert("DRONE MATCH:  %s!%s@%s gecos mask: %s",
#endif
                 u->nick, u->username, u->hostname,
                 de->name);
#endif
      slog(DRONE_LOG, L_NOTICE, "GE %s!%s@%s %s", u->nick, u->username,
           u->hostname, de->name);
      return ;
    }
  }
}

void CheckChannelDrones(struct User *u, struct Channel *c)
{
  struct DroneEntry *de;
  dlink_node *dl;
  dlink_node *dadd;
  struct DroneNode *dn;

  if (IsOper(u))
    return ;

  DLINK_FOREACH(dl, MonServ.drone_users.head)
  {
    dn = dl->data;

    if (dn->u == u)
      return ;
  }

  DLINK_FOREACH(dl, MonServ.channel_drone_list.head)
  {
    de = dl->data;
    if (match(de->name, c->name) == 0)
    {
      dadd = dlink_create();
      dn = smalloc(sizeof(struct DroneNode));
      memcounts.dronenode++;
      dn->u = u;
      snprintf(dn->reason, LINE, "channel %s", de->name);
      dlink_add(dn, dadd, &MonServ.drone_users);
      de->hits++;
#if defined(DRONES_ALERT)
#ifdef DRONES_CHANNEL

      dronealert("DRONE MATCH:  %s!%s@%s channel mask: %s",
#else
      alert("DRONE MATCH:  %s!%s@%s channel mask: %s",
#endif
                 u->nick, u->username, u->hostname,
                 de->name);
#endif
      slog(DRONE_LOG, L_NOTICE, "CH %s!%s@%s %s", u->nick, u->username,
           u->hostname, de->name);
      return ;
    }
  }
}

#ifdef TRACK_CTCPVERSION
void CheckVersionDrones(struct User *u, char *version)
{
  struct DroneEntry *de;
  dlink_node *dl;
  dlink_node *dadd;
  struct DroneNode *dn;

  if (IsOper(u))
    return ;

  DLINK_FOREACH(dl, MonServ.drone_users.head)
  {
    dn = dl->data;

    if (dn->u == u)
      return ;
  }

  DLINK_FOREACH(dl, MonServ.version_drone_list.head)
  {
    de = dl->data;
    if (match(de->name, version) == 0)
    {
      dadd = dlink_create();
      dn = smalloc(sizeof(struct DroneNode));
      memcounts.dronenode++;
      dn->u = u;
      snprintf(dn->reason, LINE, "version %s", de->name);
      dlink_add(dn, dadd, &MonServ.drone_users);
      de->hits++;
#if defined(DRONES_ALERT)
#ifdef DRONES_CHANNEL

      dronealert("DRONE MATCH:  %s!%s@%s version mask: %s",
#else
      alert("DRONE MATCH:  %s!%s@%s version mask: %s",
#endif
                 u->nick, u->username, u->hostname,
                 de->name);
#endif
      slog(DRONE_LOG, L_NOTICE, "VE %s!%s@%s %s", u->nick, u->username,
           u->hostname, de->name);
      return ;
    }
  }
}
#endif

void RemoveDrone(struct User *u)
{
  dlink_node *dl = dlink_find(u, &MonServ.drone_users);
  struct DroneNode *dn;

  DLINK_FOREACH(dl, MonServ.drone_users.head)
  {
    dn = dl->data;

    if (dn->u == u)
    {
      dlink_delete(dl, &MonServ.drone_users);
      memcounts.dronenode--;
      free(dn);
      dlink_free(dl);
      return ;
    }
  }
}

void RemoveDroneDL(dlink_node *dl)
{
  if (dl == NULL)
    return ;

  dlink_delete(dl, &MonServ.drone_users);
  memcounts.dronenode--;
  free(dl->data);
  dlink_free(dl);
}
#endif

#endif
